Skip to content

fix(online-update): stop polling update progress without permission - #904

Open
kittors wants to merge 1 commit into
devfrom
fix/update-poll-permission-guard
Open

fix(online-update): stop polling update progress without permission#904
kittors wants to merge 1 commit into
devfrom
fix/update-poll-permission-guard

Conversation

@kittors

@kittors kittors commented Aug 8, 2026

Copy link
Copy Markdown
Owner

问题

线上抓包(CDP network + 服务端 journalctl)显示,一个空闲面板标签页每分钟发约 33 个请求,永不停止:

  • GET /v0/management/update/progress 每 ~2s(本部署没有 updater sidecar,后端 502,前端 5s 超时中断)
  • GET /v0/management/update/events 每 ~3s 重连(返回 2xx 但不投递事件,link 永远停在 reconnecting,于是轮询永远不停)

对没有 system.status.read 的租户管理员,每次 progress 请求都是 403。后端逐条写审计日志,这就是 audit-logs 页面里 28435 条 update · progress 的来源。

改动

  • 权限门控OnlineUpdateProvider 从"所有已登录用户"改为额外要求 can("system.status.read") —— 与后端 /update/* 只读所需权限、以及 systemRoute.requiredPermission 完全一致。用不了这个功能的人不再去轮询它。

  • 自适应节奏:以"是否有更新在执行中"(isRunningProgress(latest),或 apply 之后 5 分钟的 active hint)为准。

    状态 轮询 SSE 重连
    运行中 2s(不变) 300ms → 3s(不变)
    空闲 30s,失败翻倍至 5min 3s → 30s

    更新过程中的行为与原来完全一致 —— 容器重启期间的快速重连正是它存在的理由;空闲标签页从 ~33 次/分钟降到 ~4 次/分钟。

  • 403 直接停机:权限被拒不是暂时故障,重试改变不了结果,只会继续灌审计日志。订阅者全部离开时重置,重新登录不会继承这个状态。

  • 审计结果三态deniedfailed 此前渲染成同一个红色"失败"徽章,掩盖了这个页面上最该区分的东西。现在 success 绿 / denied 琥珀 / failed 红,新增 identity_admin.result_denied(zh-CN / en / ru)。

后端配套 PR:kittors/CliRelay#863(审计写入策略 + 保留策略 + 历史清理),建议后端先合并部署。

验证

本地完整 ./scripts/ci-pr.sh 通过:lintdesign:check、全量 vitest、buildbundle:diff、12 条 @critical e2e。

测试改动:

  • reconnects quickly after the stream drops during a run:原用例保留原意(更新中必须快速重连),补上"先确立有 run 在执行"的前提。
  • backs off instead of hammering while nothing is in flight:空闲时 1 秒内最多 1 次重连、轮询只有订阅那一次。
  • stops entirely once the server refuses the operator:403 之后不再有任何请求。
  • distinguishes a refused result from a failed one:审计页 denied 徽章。

🤖 Generated with Claude Code

An idle panel tab issued ~33 requests a minute, forever, against endpoints it
often had no right to call. Measured on a live instance: GET /update/progress
every ~2s and an /update/events reconnect every ~3s, from every open tab, on a
deployment with no updater sidecar where each of those fails.

The backend audited each refusal, which is how the governance page ended up
holding 28,435 rows of one operator being told no.

Three causes, three fixes:

- The provider subscribed every authenticated operator. It is now gated on
  system.status.read, the permission the update endpoints actually require and
  the one the system route already declares.
- The transport treated "no run in flight" the same as "mid-update", so the
  aggressive cadence the container restart needs ran around the clock. Cadence
  now follows what is happening: unchanged while a run is in flight (2s polls,
  sub-second reconnects), 30s polls backing off to 5m and 3-30s reconnects when
  idle.
- A 403 is not transient. The transport now stops instead of retrying, and
  resets when the last subscriber leaves so a new session is not stuck with it.

The audit page also rendered "denied" and "failed" as the same red badge, hiding
the distinction that matters most there: refused is not the same as errored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant